package com.then.basic.socket.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import org.greenrobot.eventbus.EventBus;


import android.util.Log;

import com.blankj.utilcode.util.NetworkUtils;
import com.then.basic.socket.SocketConfig;
import com.then.basic.socket.core.error.CommonError;
import com.then.basic.socket.data.ProtocolCode;
import com.then.basic.socket.utils.I366PickUtil;

/**
 * SOCKET连接接口 提供长短两种SOCKET连接
 *
 * @author shenxing.ruan
 */
public class Registration {
    private TcpRecvThread tcpRecvThread;
    private DataOutputStream dataOutput;
    private DataInputStream dataInput;
    private Socket socket;
    private String ip;
    private int port;
    /**
     * socketType类型 0:短连接 1:长连接
     **/
    private int socketType = 0;

    /**
     * 服务器类型  1:资源服务器  2: 游戏服务器
     **/
    private int serverType = SERVER_TYPE_RES;
    public static final int SERVER_TYPE_RES = 1;
    public static final int SERVER_TYPE_GAME = 2;

    public static final int CONNECT_TIME_OUT = 15000;

    private static Map<String, Registration> regMaps = new HashMap<String, Registration>();
    private static final String TAG = "Registration";
    private TimeThread timeThread;
    private CheckOnlineTask checkThread;
    private volatile boolean isTimeClose = false;
    private int sleepTime = 30000;

    public Registration() {
    }

    public static Registration bindSocket(int serverType, Socket socket) {
        Registration registration = new Registration();
        registration.getLongReg(serverType, socket);
        return registration;
    }

    /**
     * @param socketType 0:短连接 1:长连接
     */
    public void setSocketType(int socketType) {
        this.socketType = socketType;
    }

    public int getSocketType() {
        return socketType;
    }

    /**
     * 获取已经存在长连接SOCKET
     *
     * @param config
     * @return
     */
    public static synchronized Registration getYetReg(Object[] config) {
        if (config == null || config.length != 2 || config[0] == null || config[1] == null) {
            return null;
        }
        return regMaps.get((String) config[0] + ":" + config[1].toString());
    }

    public static synchronized void moueOutYetReg(Object[] config) {
        if (config == null || config.length < 2 || config[0] == null || config[1] == null) {
            return;
        }
        String key = (String) config[0] + ":" + config[1].toString();
        Registration reg = regMaps.remove(key);
        if (reg != null) {
            reg.close();
        }
        Log.i(TAG, "Remove socket " + key + " exists:" + (reg != null));
    }

    /**
     * @param config     ip+port
     * @param serverType 1:资源服务器  2: 游戏服务器
     */
    public void getLongReg(Object[] config, int serverType) {
        getLongReg(config, serverType, null);
    }

    public void getLongReg(int serverType, Socket existSocket) {
        getLongReg(new Object[]{existSocket.getInetAddress().getHostAddress(), existSocket.getPort()},
                serverType, existSocket);
    }

    public void getLongReg(Object[] config, int serverType, Socket existSocket) {
        synchronized (Registration.class) {
            Log.i(TAG, "Connect to server, type=" + serverType);
            this.serverType = serverType;
            if (serverType == SERVER_TYPE_RES) {
                sleepTime = 20000;
            } else if (serverType == SERVER_TYPE_GAME) {
                sleepTime = 5000;
            }

            // 移除原有的连接
            String key = config[0] + ":" + config[1];
            Registration oldReg = regMaps.get(key);
            if (oldReg != null) {
                Log.w(TAG, "create new long reg without closing old!");
                oldReg.close();
            }

            socket = existSocket;
            setSocketType(1);
            init(config);

            // 放入新的连接
            if (socket != null && socket.isConnected()) {
                regMaps.put(key, this);
                startSyncSignalThread();
            }
        }
    }

    public static synchronized void clearLongRegs() {
        for (Registration reg : regMaps.values()) {
            reg.close();
        }
        regMaps.clear();
    }

    public int getServerType() {
        return serverType;
    }

    private class TimeThread extends Thread {
        private static final String TAG = "RegTimer";

        @Override
        public void run() {
            setName("Reg Heartbeat" + serverType);
            Log.i(TAG, "start");
            try {
                while (!isTimeClose) {
                    Thread.sleep(sleepTime);
                    SyncSignal();
                }
            } catch (InterruptedException e) {
                if (isTimeClose) {
                    Log.d(TAG, "run: interrupted");
                } else {
                    e.printStackTrace();
                }
            }
            Log.i(TAG, "end");
        }
    }

    public void SyncSignal() {
        if (socketIsConnect()) {
            if (serverType == 1) {
                publishByte(I366PickUtil.packAll(new Object[][]{}, ProtocolCode.REQ_SIGNAL_RESOURCE, SocketConfig.getUserId()));
            } else if (serverType == 2) {
                publishByte(I366PickUtil.packAll(new Object[][]{}, ProtocolCode.REQ_SIGNAL_GAME, SocketConfig.getUserId()));
            }
        }
    }

    /**
     * 短连接的SOCKET 连接完服务器收到返回数据包后 就关闭SOCKET
     *
     * @return
     */
    public void getShortReg(Object[] config) {
        setSocketType(0);
        init(config);
    }

    public void init(Object[] config) {
        if (NetworkUtils.isAvailableByPing()) {
            try {
                setIp((String) (config[0]));
                setPort(config[1].toString());
                if (socket == null) {
                    socket = new Socket();
                    socket.connect(new InetSocketAddress(getIp(), getPort()), CONNECT_TIME_OUT);
                }
                try {
                    socket.setSoTimeout(sleepTime * 3);
                } catch (SocketException e) {
                    Log.e(TAG, "error when setSoTimeout", e);
                }
                dataOutput = new DataOutputStream(socket.getOutputStream());
                dataInput = new DataInputStream(socket.getInputStream());
                tcpRecvThread = new TcpRecvThread();
                tcpRecvThread.SetDataInput(dataInput, this);  //采用消息机制来管理tcp收数据线程
                tcpRecvThread.startTask();
                //	tcpRecvThread.start();
            } catch (UnknownHostException e) {
                Log.e(TAG, "logservice init UnknownHostException");
                EventBus.getDefault().post(CommonError.NetworkErrorInfo.ConnectServerFailMessage);
                isTimeClose = true;
                e.printStackTrace();
            } catch (IOException e) {
                Log.e(TAG, "logservice init IOException");
                socket = null;
                EventBus.getDefault().post(CommonError.NetworkErrorInfo.ConnectServerFailMessage);
                isTimeClose = true;
                e.printStackTrace();
            }
        } else {
            EventBus.getDefault().post(CommonError.NetworkErrorInfo.NetworkNotAvailableMessage);
        }
    }

    private synchronized void startSyncSignalThread() {
        if (isTimeClose) {
            return;
        }

        if (getSocketType() == SERVER_TYPE_RES && timeThread == null) {
            Log.d(TAG, "Start SyncSignalThread");
            timeThread = new TimeThread();
            timeThread.setName("SyncSignalThread " + serverType);
            timeThread.start();
            if (checkThread == null) {//启动检测线程
                checkThread = new CheckOnlineTask(serverType);
                checkThread.start();
            }
        }
    }

    public void setUpdatetime() {
        if (checkThread != null) {
            checkThread.setUpdateTime(System.currentTimeMillis());
        }
    }

    public synchronized void publishByte(byte[] bytes) {
        boolean bSendSuccess = false;
        try {
            if (dataOutput != null) {
                dataOutput.write(bytes, 0, bytes.length);
                dataOutput.flush();
                bSendSuccess = true;
            }
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "IOException when publish bytes!");
        }

        if (!bSendSuccess) {
            Log.e(TAG, "Send Package Fail!");

            if (!isTimeClose) {
                //应该发送msg消息 ，通知发包失败
                Log.e(TAG, "Send 'Send Package Fail' Message. ServerType:" + serverType);
                EventBus.getDefault().post(new CommonError.NetworkErrorInfo(
                        CommonError.NetworkErrorInfo.ErrorType.SendPackageFail, serverType, 3));
            }
            close();
        }
    }

    public boolean socketIsConnect() {
        if (socket != null) {
            if (socket.isClosed()) {
                socket = null;
                return false;
            }
            return true;
        } else {
            return false;
        }
    }

    public synchronized void close() {
        if (isTimeClose) {
            return;
        }
        Log.i(TAG, "Reg closing, type=" + serverType);
        isTimeClose = true;
        if (timeThread != null) {
            timeThread.interrupt();
            timeThread = null;
        }
        if (checkThread != null) {
            checkThread.stop();
            checkThread = null;
        }
        if (tcpRecvThread != null) {
            tcpRecvThread.stopThread();
        }
        if (socket != null) {
            try {
                if (dataOutput != null) {
                    dataOutput.close();
                    dataOutput = null;
                }
                if (dataInput != null) {
                    dataInput.close();
                    dataInput = null;
                }
                socket.close();
                socket = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (socketType == 1) {
            if (serverType == 1) {
                if (checkThread != null) {
                    checkThread.stop();
                }
            }
        }
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setPort(String port) {
        this.port = Integer.parseInt(port);
    }
}
